package com.kongnan.sidebar; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Typeface; import android.support.v4.view.MotionEventCompat; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import static android.support.v4.widget.ViewDragHelper.INVALID_POINTER; public class SideBar extends View { private static final String TAG = SideBar.class.getSimpleName(); interface OnTouchingLetterChangedListener { void onTouchingLetterChanged(String s); } private OnTouchingLetterChangedListener mOnTouchingLetterChangedListener; private String[] mLetters = null; private Paint mPaint; private int mTextColor; private int mResArrayId = R.array.letter_list; private int mChoose = -1; private final float mDensity; private float mY; private float mHalfWidth, mHalfHeight; private float mLetterHeight; private float mAnimStep; private int mTouchSlop; private float mInitialDownY; private boolean mIsBeingDragged, mStartEndAnim; private int mActivePointerId = INVALID_POINTER; private RectF mIsDownRect = new RectF(); public SideBar(Context context) { this(context, null); } public SideBar(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SideBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mPaint = new Paint(); this.mTextColor = Color.GRAY; this.mPaint.setAntiAlias(true); this.mPaint.setTextAlign(Paint.Align.CENTER); this.mPaint.setColor(this.mTextColor); this.mLetters = context.getResources().getStringArray(mResArrayId); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mDensity = getContext().getResources().getDisplayMetrics().density; setPadding(0, dip2px(20), 0, dip2px(20)); } public void setOnTouchingLetterChangedListener(OnTouchingLetterChangedListener listener) { this.mOnTouchingLetterChangedListener = listener; } private int getLettersSize() { return mLetters.length; } @Override public boolean onTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); switch (action) { case MotionEvent.ACTION_DOWN: mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mIsBeingDragged = false; final float initialDownY = getMotionEventY(ev, mActivePointerId); if (initialDownY == -1) { return false; } if (!mIsDownRect.contains(ev.getX(), ev.getY())) { return false; } mInitialDownY = initialDownY; break; case MotionEvent.ACTION_MOVE: if (mActivePointerId == INVALID_POINTER) { return false; } final float y = getMotionEventY(ev, mActivePointerId); if (y == -1) { return false; } final float yDiff = Math.abs(y - mInitialDownY); if (yDiff > mTouchSlop && !mIsBeingDragged) { mIsBeingDragged = true; } if (mIsBeingDragged) { mY = y; final float moveY = y - getPaddingTop() - mLetterHeight / 1.64f; final int characterIndex = (int) (moveY / mHalfHeight * mLetters.length); if (mChoose != characterIndex) { if (characterIndex >= 0 && characterIndex < mLetters.length) { mChoose = characterIndex; Log.d(TAG, "mChoose " + mChoose + " mLetterHeight " + mLetterHeight); // mOnTouchingLetterChangedListener.onTouchingLetterChanged(mLetters[characterIndex]); } } invalidate(); } break; case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: if (mOnTouchingLetterChangedListener != null) { if (mIsBeingDragged) { mOnTouchingLetterChangedListener.onTouchingLetterChanged(mLetters[mChoose]); } else { float downY = ev.getY() - getPaddingTop(); final int characterIndex = (int) (downY / mHalfHeight * mLetters.length); if (characterIndex >= 0 && characterIndex < mLetters.length) { mOnTouchingLetterChangedListener.onTouchingLetterChanged(mLetters[characterIndex]); } } } mStartEndAnim = mIsBeingDragged; mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; mChoose = -1; mAnimStep = 0f; invalidate(); return false; } return true; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mHalfWidth = w - dip2px(16); mHalfHeight = h - getPaddingTop() - getPaddingBottom(); float lettersLen = getLettersSize(); mLetterHeight = mHalfHeight / lettersLen; int textSize = (int) (mHalfHeight * 0.7f / lettersLen); this.mPaint.setTextSize(textSize); mIsDownRect.set(w - dip2px(16 * 2), 0, w, h); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); for (int i = 0; i < getLettersSize(); i++) { float letterPosY = mLetterHeight * (i + 1) + getPaddingTop(); float diff, diffY, diffX; if (mChoose == i && i != 0 && i != getLettersSize() - 1) { diffX = 0f; diffY = 0f; diff = 2.16f; } else { float maxPos = Math.abs((mY - letterPosY) / mHalfHeight * 7f); diff = Math.max(1f, 2.2f - maxPos); if (mStartEndAnim && diff != 1f) { diff -= mAnimStep; if (diff <= 1f) { diff = 1f; } } else if (!mIsBeingDragged) { diff = 1f; } diffY = maxPos * 50f * (letterPosY >= mY ? -1 : 1); diffX = maxPos * 100f; } canvas.save(); canvas.scale(diff, diff, mHalfWidth * 1.20f + diffX, letterPosY + diffY); if (diff == 1f) { this.mPaint.setAlpha(255); this.mPaint.setTypeface(Typeface.DEFAULT); } else { int alpha = (int) (255 * (1 - Math.min(0.9, diff - 1))); if (mChoose == i) alpha = 255; this.mPaint.setAlpha(alpha); this.mPaint.setTypeface(Typeface.DEFAULT_BOLD); } canvas.drawText(mLetters[i], mHalfWidth, letterPosY, this.mPaint); canvas.restore(); } if (mChoose == -1 && mStartEndAnim && mAnimStep <= 0.6f) { mAnimStep += 0.6f; postInvalidateDelayed(25); } else { mAnimStep = 0f; mStartEndAnim = false; } } private int dip2px(int dipValue) { return (int) (dipValue * mDensity + 0.5f); } private float getMotionEventY(MotionEvent ev, int activePointerId) { final int index = MotionEventCompat.findPointerIndex(ev, activePointerId); if (index < 0) { return -1; } return MotionEventCompat.getY(ev, index); } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); } } }